SPB Git forge

spb/immbot-ai

Public
1commits 1branches 0releases
1.5 MBsize
maindefault branch
20 days agolast push
TypeScript 98.3% CSS 0.9% Shell 0.7%
2.6 KB · 51 lines typescript
Raw Blame History
1// Panneau source d'une citation : extrait exact + contexte voisin, avec contrôle d'accès strict.2import { NextResponse } from "next/server";3import { apiError } from "@/lib/api.ts";4import { requireUser } from "@/lib/auth/session.ts";5import { all, get } from "@/lib/db/index.ts";67export async function GET(_req: Request, ctx: { params: Promise<{ chunkId: string }> }) {8  try {9    const user = await requireUser();10    const id = parseInt((await ctx.params).chunkId, 10);11    const chunk = get<{12      id: number; document_id: number; course_code: string | null; space: string; ref_type: string;13      ref_number: number | null; ref_label: string; section_title: string; title: string;14      display_content: string; week: number | null; owner_user_id: number | null;15      doc_title: string; filename: string; path: string; ingested_at: string | null;16    }>(17      `SELECT c.id, c.document_id, c.course_code, c.space, c.ref_type, c.ref_number, c.ref_label,18              c.section_title, c.title, c.display_content, c.week, c.owner_user_id,19              d.title as doc_title, d.filename, d.path, d.ingested_at20       FROM chunks c JOIN documents d ON d.id = c.document_id WHERE c.id = ?`,21      id22    );23    if (!chunk) return NextResponse.json({ error: "Source introuvable." }, { status: 404 });2425    // Contrôle d'accès : jamais l'espace professeur pour un étudiant ; les espaces étudiants26    // uniquement pour leur propriétaire ; les cours officiels selon l'inscription.27    if (chunk.space === "instructor-private" && user.role === "student") {28      return NextResponse.json({ error: "Accès refusé." }, { status: 403 });29    }30    if (chunk.space.startsWith("student-") && chunk.owner_user_id !== user.id) {31      return NextResponse.json({ error: "Accès refusé." }, { status: 403 });32    }33    if (chunk.space.startsWith("official-") && chunk.course_code) {34      const enrolled = get("SELECT 1 as ok FROM enrollments WHERE user_id = ? AND course_code = ?", user.id, chunk.course_code);35      if (!enrolled && user.role === "student") return NextResponse.json({ error: "Accès refusé." }, { status: 403 });36    }3738    const neighbors = chunk.ref_number != null39      ? all(40          `SELECT id, ref_number, title, substr(display_content, 1, 400) as preview FROM chunks41           WHERE document_id = ? AND ref_number IN (?, ?) AND id != ? ORDER BY ref_number`,42          chunk.document_id, (chunk.ref_number ?? 0) - 1, (chunk.ref_number ?? 0) + 1, chunk.id43        )44      : [];4546    return NextResponse.json({ source: chunk, neighbors });47  } catch (e) {48    return apiError(e);49  }50}51